/**
 * 
 */
package neptune.web.client.view.components;

import java.util.ArrayList;

import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.PushButton;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * 
 * Si tratta del widget che mostra le macchine virtuali presenti su una data
 * macchina fisica. La classe è usata dalla classe ClusterView.
 * 
 * @author Roberto Bifulco [RobertoBifulco.it] [info@robertoBifulco.it]
 * @author Luigi Lorenzo Arpino [luigiarpino@yahoo.it]
 */
public class NodesManager extends Composite {

	public static final String RUNNING_STATE = "RUNNING";
	public static final String BLOCKED_STATE = "BLOCKED";
	public static final String PAUSED_STATE = "PAUSED";
	public static final String SHUTDOWN_STATE = "SHUTDOWN";
	public static final String CRASHED_STATE = "CRASHED";
	public static final String DYING_STATE = "DYING";

	public static final int START_BUTTON = 0;
	public static final int PAUSE_BUTTON = 1;
	public static final int REBOOT_BUTTON = 2;
	public static final int CONSOLE_BUTTON = 3;

	private static final String BLANK_IMG = "img/blank.png";
	private static final String LOADING_IMG = "img/loading.gif";
	private static final String START_IMG = "img/start.png";
	private static final String PAUSE_IMG = "img/pause.png";
	private static final String REBOOT_IMG = "img/reboot.png";
	private static final String CONSOLE_IMG = "img/term.png";
	private static final String START_DIS_IMG = "img/start-dis.png";;
	private static final String PAUSE_DIS_IMG = "img/pause-dis.png";;
	private static final String REBOOT_DIS_IMG = "img/reboot-dis.png";;
	private static final String CONSOLE_DIS_IMG = "img/term-dis.png";;
	private static final String NOT_IN_SYNC_IMG = "img/disconnected.png";
	private static final String ERROR_IMG = "img/error.png";
	private static final String RUNNING_STATE_IMG = "img/state_start.png";
	private static final String PAUSE_STATE_IMG = "img/state_pause.png";

	public static final int UPDATING_ICON = 2;
	public static final int NOT_IN_SYNC_ICON = 1;
	public static final int RUNNING_ICON = 0;
	public static final int ERROR_ICON = 3;
	public static final int PAUSE_ICON = 4;

	private FlexTable nodeTable;
	private ArrayList<NodeCommandHandler> handlers = new ArrayList<NodeCommandHandler>(
			0);
	private NodeLiveDataProvider dataProvider;

	public static class Status {

		public static final String PAUSED = "PAUSED";
		public static final String UNKNOWN = "-";
		public static final String RUNNING = "RUNNING";

	}

	public interface NodeLiveDataProvider {
		public void getNodesInfos(NodesManager manager);

		public void getNodeInfos(String nodeId, NodesManager manager);
	}

	/**
	 * Questa interfaccia viene implementata da una classe interessata a gestire
	 * i comandi lanciati tramite questa UI, su di un nodo (identificato tramite
	 * il suo ID).
	 * 
	 * @author Roberto Bifulco [RobertoBifulco.it] [info@robertoBifulco.it]
	 * 
	 */
	public interface NodeCommandHandler {
		/**
		 * Metodo chiamato quando e' premuto il pulsante start per il nodo
		 * nodeId
		 * 
		 * @param nodeId
		 */
		public void nodeStartCommandFired(String nodeId, NodesManager sender);

		/**
		 * Metodo chiamato quando e' premuto il pulsante pause per il nodo
		 * nodeId
		 * 
		 * @param nodeId
		 */
		public void nodePauseCommandFired(String nodeId, NodesManager sender);

		/**
		 * Metodo chiamato quando e' premuto il pulsante reboot per il nodo
		 * nodeId
		 * 
		 * @param nodeId
		 */
		public void nodeRebootCommandFired(String nodeId, NodesManager sender);

		/**
		 * Metodo chiamato quando e' premuto il pulsante console per il nodo
		 * nodeId
		 * 
		 * @param nodeId
		 */
		public void nodeConsoleCommandFired(String nodeId, NodesManager sender);

		/**
		 * Method called when a cpy node img button is pressed for node nodeId
		 * 
		 * @param nodeId
		 * @param sender
		 */
		public void nodeImgCopyCommandFired(String nodeId, NodesManager sender);
	}

	/**
	 * Crea la UI
	 */
	public NodesManager(int height) {

		VerticalPanel vPanel = new VerticalPanel();
		vPanel.setWidth("100%");
		nodeTable = new FlexTable();
		nodeTable.setWidth("98%");
		ScrollPanel tableWrap = new ScrollPanel(nodeTable);
		tableWrap.setSize("100%", height + "px");

		FlexTable header = new FlexTable();
		header.setWidth("100%");
		header.setWidget(0, 0, new Image(BLANK_IMG));
		header.setText(0, 1, "Node ID");
		header.setText(0, 2, "Status");
		header.setText(0, 3, "Control Address");
		header.setText(0, 4, "Operations");

		header.getColumnFormatter().setWidth(0, "16px");
		header.getColumnFormatter().setWidth(1, "25%");
		header.getColumnFormatter().setWidth(2, "25%");
		header.getColumnFormatter().setWidth(3, "25%");
		header.getColumnFormatter().setWidth(4, "25%");

		vPanel.add(header);
		vPanel.add(tableWrap);

		// Styles
		vPanel.setStylePrimaryName("NodesManager");
		tableWrap.setStylePrimaryName("NodesManager-tableWrap");
		nodeTable.setStylePrimaryName("NodesManager-table");
		header.setStylePrimaryName("NodesManager-table");
		header.addStyleDependentName("header");

		initWidget(vPanel);
	}

	/**
	 * Rimuove un nodo dalla lista
	 * 
	 * @param nodeId
	 */
	public void removeNode(String nodeId) {
		for (int i = 0; i < nodeTable.getRowCount(); i++) {
			String actualRowId = nodeTable.getText(i, 1);

			if (actualRowId.equals(nodeId)) {
				nodeTable.removeRow(i);
				break;
			}
		}

		for (int i = 0; i < nodeTable.getRowCount(); i++) {
			if (i % 2 != 1)
				nodeTable.getRowFormatter().addStyleName(i,
						"NodesManager-table-odd");
			else
				nodeTable.getRowFormatter().removeStyleName(i,
						"NodesManager-table-odd");
		}
	}

	/**
	 * Aggiunge un nodo alla lista
	 * 
	 * @param nodeId
	 * @param status
	 * @param controlAddress
	 */
	public void addNode(String nodeId, String status, String controlAddress) {
		int insertIndex = nodeTable.getRowCount();
		nodeTable.setWidget(insertIndex, 0, new Image(BLANK_IMG));
		nodeTable.setText(insertIndex, 1, nodeId);
		nodeTable.setText(insertIndex, 2, status);
		nodeTable.setText(insertIndex, 3, controlAddress);
		nodeTable.setWidget(insertIndex, 4, buildCommandPanel(nodeId));

		nodeTable.getColumnFormatter().setWidth(0, "16px");
		nodeTable.getColumnFormatter().setWidth(1, "25%");
		nodeTable.getColumnFormatter().setWidth(2, "25%");
		nodeTable.getColumnFormatter().setWidth(3, "25%");
		nodeTable.getColumnFormatter().setWidth(4, "25%");

		if (insertIndex % 2 != 1)
			nodeTable.getRowFormatter().addStyleName(insertIndex,
					"NodesManager-table-odd");

		setButtonsEnableValues(nodeId, status);
		setIconUsingState(nodeId, status);
	}

	/**
	 * Aggiorna lo stato di un nodo gia' presente nella lista
	 * 
	 * @param nodeId
	 * @param status
	 * @param controlAddress
	 */
	public void setNodeInfos(String nodeId, String status, String controlAddress) {
		for (int i = 0; i < nodeTable.getRowCount(); i++) {
			String actualRowId = nodeTable.getText(i, 1);

			if (actualRowId.equals(nodeId)) {
				nodeTable.setText(i, 2, status);
				nodeTable.setText(i, 3, controlAddress);

				setButtonsEnableValues(nodeId, status);
				setIconUsingState(nodeId, status);
			}
		}
	}

	private void setIconUsingState(String nodeId, String status) {
		if (status.equals(Status.RUNNING))
			setVMIcon(nodeId, RUNNING_ICON);
		else if (status.equals(Status.PAUSED))
			setVMIcon(nodeId, PAUSE_ICON);
		else if (status.equals(Status.UNKNOWN))
			setVMIcon(nodeId, ERROR_ICON);
	}

	/**
	 * Aggiorna lo stato di un nodo gia' presente nella lista
	 * 
	 * @param nodeId
	 * @param status
	 */
	public void setNodeInfos(String nodeId, String status) {
		for (int i = 0; i < nodeTable.getRowCount(); i++) {
			String actualRowId = nodeTable.getText(i, 1);

			if (actualRowId.equals(nodeId)) {
				nodeTable.setText(i, 2, status);

				setButtonsEnableValues(nodeId, status);
				setIconUsingState(nodeId, status);
			}
		}
	}

	private void setButtonsEnableValues(String nodeId, String status) {
		if (status.equals(RUNNING_STATE)) {
			setButtonEnabled(nodeId, START_BUTTON, false);
			setButtonEnabled(nodeId, PAUSE_BUTTON, true);
			setButtonEnabled(nodeId, REBOOT_BUTTON, true);
		} else if (status.equals(PAUSED_STATE)) {
			setButtonEnabled(nodeId, START_BUTTON, true);
			setButtonEnabled(nodeId, PAUSE_BUTTON, false);
			setButtonEnabled(nodeId, REBOOT_BUTTON, false);
		}
	}

	/**
	 * Imposta l'icona di updating vicino al nome del nodo
	 * 
	 * @param nodeId
	 */
	public void setVMIcon(String nodeId, int icon) {
		for (int i = 0; i < nodeTable.getRowCount(); i++) {
			String actualRowId = nodeTable.getText(i, 1);

			if (actualRowId.equals(nodeId)) {
				switch (icon) {
				case UPDATING_ICON:
					nodeTable.setWidget(i, 0, new Image(LOADING_IMG));
					break;
				case NOT_IN_SYNC_ICON:
					nodeTable.setWidget(i, 0, new Image(NOT_IN_SYNC_IMG));
					break;
				case RUNNING_ICON:
					nodeTable.setWidget(i, 0, new Image(RUNNING_STATE_IMG));
					break;
				case ERROR_ICON:
					nodeTable.setWidget(i, 0, new Image(ERROR_IMG));
					break;
				case PAUSE_ICON:
					nodeTable.setWidget(i, 0, new Image(PAUSE_STATE_IMG));
					break;
				}
			}
		}
	}

	private Panel buildCommandPanel(final String nodeId) {
		HorizontalPanel hPanel = new HorizontalPanel();

		PushButton startButton = new PushButton(new Image(START_IMG),
				new Image(START_IMG));
		startButton.setTitle("Start virtual machine");
		PushButton pauseButton = new PushButton(new Image(PAUSE_IMG),
				new Image(PAUSE_IMG));
		pauseButton.setTitle("Pause virtual machine");
		PushButton rebootButton = new PushButton(new Image(REBOOT_IMG),
				new Image(REBOOT_IMG));
		rebootButton.setTitle("Reboot virtual machine");
		
		Button copyImgButton = new Button("Save as template");
		
		PushButton consoleButton = new PushButton(new Image(CONSOLE_IMG),
				new Image(CONSOLE_IMG));
		consoleButton.setTitle("Open SSH console to virtual machine");

		startButton.getUpDisabledFace().setImage(new Image(START_DIS_IMG));
		startButton.getDownDisabledFace().setImage(new Image(START_DIS_IMG));

		pauseButton.getUpDisabledFace().setImage(new Image(PAUSE_DIS_IMG));
		pauseButton.getDownDisabledFace().setImage(new Image(PAUSE_DIS_IMG));

		rebootButton.getUpDisabledFace().setImage(new Image(REBOOT_DIS_IMG));
		rebootButton.getDownDisabledFace().setImage(new Image(REBOOT_DIS_IMG));

		consoleButton.getUpDisabledFace().setImage(new Image(CONSOLE_DIS_IMG));
		consoleButton.getDownDisabledFace()
				.setImage(new Image(CONSOLE_DIS_IMG));

		startButton.addClickListener(new ClickListener() {
			public void onClick(Widget sender) {
				fireStartNodeCommand(nodeId);
			}
		});
		pauseButton.addClickListener(new ClickListener() {
			public void onClick(Widget sender) {
				firePauseNodeCommand(nodeId);
			}
		});
		rebootButton.addClickListener(new ClickListener() {
			public void onClick(Widget sender) {
				fireRebootNodeCommand(nodeId);
			}
		});
		copyImgButton.addClickListener(new ClickListener() {
			public void onClick(Widget sender) {
				fireCopyImgCommand(nodeId);
			}
		});
		consoleButton.addClickListener(new ClickListener() {
			public void onClick(Widget sender) {
				fireConsoleNodeCommand(nodeId);
			}
		});

		hPanel.add(startButton);
		hPanel.add(pauseButton);
		hPanel.add(rebootButton);
		hPanel.add(copyImgButton);
		hPanel.add(new Image(BLANK_IMG));
		hPanel.add(consoleButton);

		hPanel.setStylePrimaryName("NodesManager-commandPanel");
		startButton.setStylePrimaryName("NodesManager-commandButton");
		pauseButton.setStylePrimaryName("NodesManager-commandButton");
		rebootButton.setStylePrimaryName("NodesManager-commandButton");
		copyImgButton.setStylePrimaryName("NodesManager-commandButton");
		consoleButton.setStylePrimaryName("NodesManager-commandButton");

		return hPanel;
	}

	/**
	 * Imposta il valore enabled per il pulsante indicato, riferito al nodo il
	 * cui identificativo e' nodeId.
	 * 
	 * @param nodeId
	 * @param button
	 * @param enabled
	 */
	public void setButtonEnabled(String nodeId, int button, boolean enabled) {
		for (int i = 0; i < nodeTable.getRowCount(); i++) {
			String actualRowId = nodeTable.getText(i, 1);

			if (actualRowId.equals(nodeId)) {
				HorizontalPanel commandsPanel = (HorizontalPanel) nodeTable
						.getWidget(i, 4);
				PushButton buttonWidget = (PushButton) commandsPanel
						.getWidget(button);
				buttonWidget.setEnabled(enabled);
			}
		}
	}

	/**
	 * Rimuove tutti i nodi dalla lista
	 * 
	 */
	public void removeAllNodes() {
		int rowCount = nodeTable.getRowCount();
		for (int i = 0; i < rowCount; i++) {
			nodeTable.removeRow(0);
		}
	}

	/**
	 * Aggiunge un {@link NodeCommandHandler}, i cui metodi saranno chiamati
	 * quando verra' premuto uno dei pulsanti corrispondenti ad uno dei nodi.
	 * 
	 * @param handler
	 */
	public void addNodeCommandHandler(NodeCommandHandler handler) {
		handlers.add(handler);
	}

	/**
	 * Rimuove un {@link NodeCommandHandler}
	 * 
	 * @param handler
	 */
	public void removeNodeCommandHandler(NodeCommandHandler handler) {
		handlers.remove(handler);
	}

	/**
	 * Lancia un evento di Start per nodeId
	 */
	public void fireStartNodeCommand(String nodeId) {
		for (NodeCommandHandler handler : handlers) {
			handler.nodeStartCommandFired(nodeId, this);
		}
	}

	/**
	 * Lancia un evento di Pause per nodeId
	 */
	public void firePauseNodeCommand(String nodeId) {
		for (NodeCommandHandler handler : handlers) {
			handler.nodePauseCommandFired(nodeId, this);
		}
	}

	/**
	 * Lancia un evento di Reboot per nodeId
	 */
	public void fireRebootNodeCommand(String nodeId) {
		for (NodeCommandHandler handler : handlers) {
			handler.nodeRebootCommandFired(nodeId, this);
		}
	}

	/**
	 * Lancia un evento di Console per nodeId
	 */
	public void fireConsoleNodeCommand(String nodeId) {
		for (NodeCommandHandler handler : handlers) {
			handler.nodeConsoleCommandFired(nodeId, this);
		}
	}

	/**
	 * Throws a Copy Imgage File event for node nodeId
	 * 
	 * @param nodeId
	 */
	public void fireCopyImgCommand(String nodeId) {
		for (NodeCommandHandler handler : handlers)
			handler.nodeImgCopyCommandFired(nodeId, this);
	}

	/**
	 * @param dataProvider
	 *            the dataProvider to set
	 */
	public void setDataProvider(NodeLiveDataProvider dataProvider) {
		this.dataProvider = dataProvider;
	}

	/**
	 * @return the dataProvider
	 */
	public NodeLiveDataProvider getDataProvider() {
		return dataProvider;
	}

	/**
	 * Ricarica i dati tramite il {@link NodeLiveDataProvider} per il dato nodo
	 * 
	 * @param nodeId
	 */
	public void refreshSingleNodeData(String nodeId) {
		dataProvider.getNodeInfos(nodeId, this);
	}

	/**
	 * Ricarica i dati tramite il {@link NodeLiveDataProvider}
	 */
	public void refreshData() {
		removeAllNodes();
		dataProvider.getNodesInfos(this);
	}

	/**
	 * Rimuove tutti gli handler registrati su questo oggetto e tutti i nodi in
	 * lista.
	 */
	public void reset() {
		removeAllNodes();
		handlers.clear();
	}
}
